#!/usr/bin/python

# Copyright 2011 Mark Holmquist
#
# This file is part of stattr.
# stattr is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# stattr is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with stattr.  If not, see <http://www.gnu.org/licenses/>.

import json
import urlparse
import hashlib
import random
import os
import sys

from flask import Flask, request
import psycopg2
import psycopg2.extras

def determine_path():
    """Borrowed from wxglade.py"""
    try:
        root = __file__
        if os.path.islink(root):
            root = os.path.realpath(root)
        return os.path.dirname (os.path.abspath(root))
    except:
        print "I'm sorry, but something is wrong."
        print "There is no __file__ variable. Please contact the author."
        sys.exit()

def make_response(cb, response):
    return cb + '(' + json.dumps(response) + ');'

def send_error(request, message):
    return make_response(request.args.get('callback', ''),
                         {'error': message})

def send_file(filename):
    return open(determine_path() + filename).read()

def read_conf():
    config = {}
    ourconf = open(determine_path() + '/stattrd.conf')
    for line in ourconf.read().split('\n'):
        if len(line.split('=')) > 1:
            config[line.split('=')[0]] = line.split('=')[1]
        else:
            config[line.split('=')[0]] = ''
    return config

def build_database(database, config):
    cur = database.cursor()

    # Create config table, if it doesn't exist
    cur.execute("SELECT * FROM pg_tables WHERE tablename='stattrconf'")
    if not cur.fetchone():
        cur.execute("CREATE TABLE stattrconf "\
                    "(sitename varchar, logo varchar, location varchar)")

    # Put in config data, if it's not already there
    cur.execute("SELECT * FROM stattrconf")
    if config['sitename']\
       and config['logourl']\
       and config['location']\
       and not cur.fetchone():
        cur.execute("INSERT INTO stattrconf VALUES ('%s', '%s', '%s')"\
                        % (config['sitename'],
                           config['logourl'],
                           config['location']))
    
    # Create table to list activities, if it doesn't exist
    cur.execute("SELECT * FROM pg_tables WHERE tablename='stattrtbls'")
    if not cur.fetchone():
        database.cursor().execute("CREATE TABLE stattrtbls (id varchar, "\
                                  "activity varchar, descr text, "\
                                  "official varchar[], fields varchar[], "\
                                  "types varchar[])")

    # Create users table if not there
    cur.execute("SELECT * FROM pg_tables WHERE tablename='stattrusers'")
    if not cur.fetchone():
        cur.execute("CREATE TABLE stattrusers (username varchar, "\
                    "password varchar, admin int, fullname varchar, "\
                    "profile text)")
        cur.execute("INSERT INTO stattrusers VALUES ('%s', '%s', 1, "\
                    "'admin', '')"\
                        % (config['adminuser'],
                           hashlib.sha1(config['adminpass']).hexdigest()))
    database.commit()

# Server start function
def start():
    config = read_conf()
    sessions = {}
    database = psycopg2.connect(database=config['dbname'],
                                host=config['hostname'],
                                port=int(config['port']),
                                user=config['dbuser'],
                                password=config['dbpass'])
    tplList = ['home', 'header', 'login', 'userbar', 'admin',
               'eventcreate', 'eventhome', 'usercreate', 'resultsadd']
    build_database(database, config)
    stattr_server = Flask(__name__)

    # -------------------------------
    #       Utility functions
    # -------------------------------

    def make_session(username, hostname, admin):
        idNum = hashlib.sha1(username + hostname + \
                str(random.random())).hexdigest()
        sessions[idNum] = (username, hostname, admin)
        return idNum

    # Redirect / to /static/index.html

    @stattr_server.route('/')
    def index_page():
        return send_file('/static/index.html')

    # -------------------
    #    GET functions
    # -------------------
    
    @stattr_server.route('/tpls.json')
    def get_tpls():
        which = request.args.get('which', '')
        if which not in tplList and which != 'all':
            return send_error('template does not exist')
        else:
            ourpath = determine_path()
            tpls = {}
            if which == 'all' or which == '':
                which = tplList
            for tpl in which:
                openStr = ourpath + '/tpls/' + tpl + '.tpl'
                tpls[tpl] = open(openStr, 'r').read()
            return make_response(request.args.get('callback', ''), tpls)
        
    @stattr_server.route('/conf.json')
    def get_conf():
        cur = database.cursor()
        cur.execute("SELECT * FROM stattrconf")
        sitename, logo, location = cur.fetchone()
        return make_response(request.args.get('callback', ''),
                             {'sitename': sitename, 
                              'logo': logo,
                              'location': location})

    @stattr_server.route('/events.json')
    def get_events():
        cur = database.cursor()
        cur.execute("SELECT * FROM stattrtbls")
        raw_results = cur.fetchall()
        eventslist = []
        for r in raw_results:
            event = {'id': r[0], 'activity': r[1], 'descr': r[2]}
            eventslist.append(event)
        return make_response(request.args.get('callback', ''),
                             {'events': eventslist})

    @stattr_server.route('/isadmin.json')
    def get_admin():
        username = request.args.get('username', '')
        session = request.args.get('session', '')
        if session == '' or username == '':
            return send_error(request, 'need a session id and username to '\
                                       'complete this request')
        elif not session in sessions:
            return send_error(request, 'session does not exist')
        elif sessions[session][0] != username:
            return send_error(request, 'username does not match our session '\
                              'information')
        elif sessions[session][1] != request.remote_addr:
            return send_error(request, 'request address does not match our '\
                                       'session information')
        else:
            return make_response(request.args.get('callback', ''),
                                 {'isAdmin': sessions[session][2]})

    @stattr_server.route('/event.json')
    def get_event():
        if request.args.get('_method', '') == 'POST':
            return add_event(request)
        cur = database.cursor(cursor_factory=psycopg2.extras.DictCursor)
        eventid = request.args.get('id', '')
        cur.execute("SELECT * FROM pg_tables WHERE tablename=%s", [eventid])
        if eventid == '':
            return send_error(request, 'need to know what event you '\
                                       'want to complete this request')
        elif not cur.fetchone():
            return send_error(request, 'that event does not seem to exist....')
        else:
            cur.execute("SELECT * FROM stattrtbls WHERE id=%s", [eventid])
            tblinfo = list(cur.fetchone())
            cur.execute("SELECT * FROM %s" % eventid)
            eventsinfo = cur.fetchall()
            results = []
            page = []
            if len(eventsinfo) > 0:
                for happening in eventsinfo:
                    if len(page) > 9:
                        results.insert(0, page)
                        page = []
                    thisresult = []
                    for field in happening:
                        thisresult.append(field)
                    page.insert(0, thisresult);
                results.insert(0, page)
        pagenum = request.args.get('page', '')
        if pagenum != '':
            resultobj = {'events': results[pagenum]} 
        elif len(eventsinfo) <= 0:
            resultobj = {'table': tblinfo}
        else:
            resultobj = {'table': tblinfo, 'events': results[0],
                         'pages': len(results)}

        return make_response(request.args.get('callback', ''),
                             resultobj)

    @stattr_server.route('/user.json')
    def get_user():
        if request.args.get('_method', '') == 'POST':
            return add_user(request)
        else:
            return send_error(request, 'users are only addable right now')

    # -----------------
    # POST functions
    # -----------------

    @stattr_server.route('/login.json')
    def login():
        cur = database.cursor()
        username = request.args.get('username', '')
        password = request.args.get('password', '')
        if password == '' or username == '':
            return send_error(request, 'need to supply username and password')
        cur.execute("SELECT * FROM stattrusers WHERE username=%s",
                    [username])
        dbuser = cur.fetchone()
        if not dbuser:
            return send_error(request, 'user does not exist')
        if password != dbuser[1]:
            return send_error(request, 'incorrect password')
        sessionid = make_session(username, request.remote_addr, dbuser[2])
        return make_response(request.args.get('callback', ''),
                             {'session': sessionid, 'username': username,
                              'admin': dbuser[2]})

    def add_event(request):
        activity = request.args.get('activity', '')
        variables = request.args.getlist('variables')
        types = request.args.getlist('types')
        officials = request.args.get('officials', '')
        descr = request.args.get('descr', '')
        eventid = request.args.get('id', '')
        if activity == ''\
           or variables == ''\
           or types == ''\
           or officials ==''\
           or descr == '':
            return send_error('data not sufficient to create event')
        elif eventid == '':
            eventid = activity.split()[0]
            eventid += hashlib.sha1(descr).hexdigest()[0:8]
        cur = database.cursor()
        cur.execute("SELECT * FROM pg_tables WHERE tablename=%s",
                    [eventid])
        if not cur.fetchone(): # create table, if it doesn't exist
            varstring = "participants varchar[], "
            typestring = "'{\"varchar\", "
            fieldstring = "'{\"participants\", "
            for pair in zip(variables, types):
                varstring += '%s %s[], ' % pair
                typestring += '"%s", ' % pair[1]
                fieldstring += '"%s", ' % pair[0]
            typestring = typestring[:-2] + "}'"
            fieldstring = fieldstring[:-2] + "}'"
            cur.execute("CREATE TABLE %s (%s)" % (eventid,
                                                  varstring[:-2]))
            officialstring = "'{"
            officials = officials.split(',')
            for official in officials:
                officialstring += '"%s", ' % official.split()[0]
            officialstring = officialstring[:-2] + "}'"
            cur.execute("INSERT INTO stattrtbls VALUES "\
                        "('%s', '%s', '%s', %s, %s, %s)"\
                        % (eventid, activity, descr, officialstring,
                           fieldstring, typestring))
            database.commit()
            cur.close()
            return make_response(request.args.get('callback', ''), {})
        else:
            return send_error(request,
                              'event already exists, create a unique ID')

    def add_user(request):
        username = request.args.get('newuser', '')
        password = request.args.get('password', '')
        fullname = request.args.get('fullname', '')
        admin = request.args.get('admin', '')
        profile = request.args.get('profile', '')
        cur = database.cursor()
        cur.execute("SELECT * FROM stattrusers WHERE username=%s", [username])
        if username == ''\
           or password == ''\
           or fullname == ''\
           or admin == '':
            return send_error(request, 'data not sufficient to create user')
        elif cur.fetchone():
            return send_error(request, 'user already exists')
        else:
            cur.execute("INSERT INTO stattrusers VALUES (%s, %s, %s, %s, %s)",
                        (username, password, admin, fullname, profile))
            database.commit()
            cur.close()
            return make_response(request.args.get('callback', ''), {})
    
    @stattr_server.route('/results.json')
    def add_results():
        username = request.args.get('username', '')
        session = request.args.get('session', '')
        event = request.args.get('event', '')
        results = request.args.getlist('results')
        if username == ''\
           or session == '':
            return send_error(request, 'need to be logged in to add results')
        elif not session in sessions\
           or sessions[session][0] != username\
           or sessions[session][1] != request.remote_addr:
            return send_error(request,
                              'session data does not match query string')
        elif event == ''\
           or results == '':
            return send_error(request, 'server needs data to create a resultset')
        else:
            cur = database.cursor()
            print results
            results = [field.split(',') for field in results]
            cur.execute("SELECT * FROM stattrtbls WHERE id=%s", [event])
            fields = list(cur.fetchone())[5]
            while 'bool' in fields:
                ouri = fields.index('bool')
                fields[ouri] = 'done'
                results[ouri] = [thisbool == 'true'
                                 for thisbool in results[ouri]]
            while 'int' in fields or 'double' in fields:
                ouri = fields.index('int') or fields.index('double')
                thistype = fields[ouri]
                fields[ouri] = 'done'
                results[ouri] = [(int(thisnum) if thistype == 'int'
                                  else float(thisnum)) 
                                 for thisnum in results[ouri]]
            execstr = 'INSERT INTO ' + event + ' VALUES (' 
            execstr += ', '.join(['%s' for i in results]) + ')'
            cur.execute(execstr, results)
            database.commit();
            return make_response(request.args.get('callback', ''), {})

    stattr_server.run(debug=True)

